package cool.houge.pig.infra.service;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import cool.houge.pig.BizCodeException;
import cool.houge.pig.BizErrors;
import cool.houge.pig.RegType;
import cool.houge.pig.UserStatus;
import cool.houge.pig.model.AuthClient;
import cool.houge.pig.model.User;
import cool.houge.pig.repository.user.UserRepository;
import cool.houge.pig.repository.weixin.Jscode2SessionInput;
import cool.houge.pig.repository.weixin.Jscode2SessionResult;
import cool.houge.pig.repository.weixin.RemoteWeixinRepository;
import cool.houge.pig.service.auth.AuthTokenInput;
import cool.houge.pig.service.auth.AuthTokenOutput;
import cool.houge.pig.service.auth.BuildTokenMeta;
import cool.houge.pig.service.auth.SharedAuthService;
import cool.houge.pig.service.auth.SharedTokenService;
import cool.houge.pig.service.auth.WeixinAuthService;
import cool.houge.pig.service.webhook.SharedWebhookService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

/**
 * 微信认证服务实现.
 *
 * @author KK (kzou227@qq.com)
 */
@Service
public class WeixinAuthServiceImpl implements WeixinAuthService {

  private static final Logger log = LogManager.getLogger();
  private final UserRepository userRepository;
  private final SharedAuthService authService;
  private final SharedTokenService tokenService;
  private final SharedWebhookService webhookService;
  private final RemoteWeixinRepository weixinRepository;

  public WeixinAuthServiceImpl(
      UserRepository userRepository,
      SharedAuthService authService,
      SharedTokenService tokenService,
      SharedWebhookService webhookService,
      RemoteWeixinRepository weixinRepository) {
    this.userRepository = userRepository;
    this.authService = authService;
    this.tokenService = tokenService;
    this.webhookService = webhookService;
    this.weixinRepository = weixinRepository;
  }

  @Transactional
  @Override
  public Mono<AuthTokenOutput> xcx(AuthTokenInput input) {
    return fetchJscode2SessionResult(input)
        .flatMap(
            t -> {
              var authClient = t.getT1();
              var result = t.getT2();
              return userRepository
                  .findByWeixinUnionId(result.getUnionid())
                  .switchIfEmpty(this.saveUser(result))
                  // 生成访问令牌返回
                  .flatMap(
                      user -> {
                        log.info("生成微信小程序访问令牌 uid={}", user.getId());
                        return tokenService.buildToken(
                            BuildTokenMeta.builder()
                                .authClient(authClient)
                                .user(user)
                                .grantType(input.getGrantType())
                                .scope(input.getScope())
                                .build());
                      });
            });
  }

  @VisibleForTesting
  Mono<User> saveUser(Jscode2SessionResult result) {
    return Mono.defer(
        () -> {
          var user =
              new User()
                  .setRegType(RegType.WEIXIN_XCX.code)
                  .setStatus(UserStatus.NORMAL.code)
                  .setWeixinUnionId(result.getUnionid());
          return userRepository
              .save(user)
              // 执行注册的 Webhook
              .delayUntil(unused -> webhookService.invokeRegister(user));
        });
  }

  @VisibleForTesting
  Mono<Tuple2<AuthClient, Jscode2SessionResult>> fetchJscode2SessionResult(AuthTokenInput input) {
    return authService
        .obtainAuthClient(input.getClientId(), input.getClientSecret(), input.getGrantType())
        .flatMap(
            authClient -> {
              var jscode2SessionInput =
                  new Jscode2SessionInput()
                      .setAppId(authClient.getWeixinAppId())
                      .setAppSecret(authClient.getWeixinAppSecret())
                      .setJsCode(input.getWeixinJsCode());
              return weixinRepository
                  .jscode2Session(jscode2SessionInput)
                  .doOnNext(
                      result -> {
                        if (result.isFailure()) {
                          log.warn(
                              "微信小程序登录认证失败 clientId={} errcode={} errmsg={}",
                              input.getClientId(),
                              result.getErrcode(),
                              result.getErrmsg());
                          throw new BizCodeException(
                              BizErrors.C911,
                              Strings.lenientFormat(
                                  "微信小程序登录认证失败 errcode=%s errmsg=%s",
                                  result.getErrcode(), result.getErrmsg()));
                        }
                      })
                  .map(result -> Tuples.of(authClient, result));
            });
  }
}
